/***********************************************************************************************************************
*                                                                                                                      *
* SPLASH build system v0.1                                                                                             *
*                                                                                                                      *
* Copyright (c) 2013 Andrew D. Zonenberg                                                                               *
* All rights reserved.                                                                                                 *
*                                                                                                                      *
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the     *
* following conditions are met:                                                                                        *
*                                                                                                                      *
*    * Redistributions of source code must retain the above copyright notice, this list of conditions, and the         *
*      following disclaimer.                                                                                           *
*                                                                                                                      *
*    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the       *
*      following disclaimer in the documentation and/or other materials provided with the distribution.                *
*                                                                                                                      *
*    * Neither the name of the author nor the names of any contributors may be used to endorse or promote products     *
*      derived from this software without specific prior written permission.                                           *
*                                                                                                                      *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED   *
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES        *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR       *
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE       *
* POSSIBILITY OF SUCH DAMAGE.                                                                                          *
*                                                                                                                      *
***********************************************************************************************************************/

#include "splashcore.h"

using namespace std;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Construction / destruction

FPGAUnitTestNode::FPGAUnitTestNode(
	BuildGraph* graph,
	string args,
	string name,
	string partition,
	bool nocswitch)
	: UnitTestNode(graph, "", name, partition)
	, m_args(args)
	, m_nocswitch(nocswitch)
	, m_cosimbridge(false)
{
	m_bitstream = NULL;
	m_binary = NULL;
	
	//TODO: locate rather than hard-coding
	//TODO: BuildGraph should have a GetHostArchitecture() routine
	m_scriptPath = graph->GetOutDir() + "/x86_64-linux-gnu/slurmwrapper";
	
	m_timelimit = 90;
}

FPGAUnitTestNode::~FPGAUnitTestNode()
{
	
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Testing

string FPGAUnitTestNode::GetHash()
{
	DebugPrintfHeading(4, "FPGAUnitTestNode::GetHash()\n");
	string hashin = "";
	
	//Update our dependencies
	UpdateDependencies();
	
	//Add data at this node
	hashin += sha256(m_name);
	hashin += sha256(m_command);
	hashin += sha256(m_scriptPath);
	
	for(auto x : m_scopenames)
		hashin += "scope" + sha256(x);
	for(auto x : m_sniffers)
		hashin += "sniff" + sha256(x);
	
	//Add data for our dependencies
	for(size_t i=0; i<m_dependencies.size(); i++)
		hashin += m_dependencies[i]->GetHashCached(); 
		
	return sha256(hashin);
}

void FPGAUnitTestNode::UpdateDependencies()
{
	DebugPrintfHeading(4, "FPGAUnitTestNode::UpdateDependencies() [name = %s]\n", m_name.c_str());
	
	UnitTestNode::UpdateDependencies();
		
	//Add dependency for target if necessary
	if(m_binary != NULL)
		m_dependencies.push_back(m_binary);
	
	//Depend on tools
	string arch = CppToolchain::CreateDefaultToolchainCached()->GetArchitecture();	
	m_dependencies.push_back(m_graph->GetTargetForArch("slurmwrapper", arch));
	m_dependencies.push_back(m_graph->GetTargetForArch("jtagd", arch));
	m_dependencies.push_back(m_graph->GetTargetForArch("jtagclient", arch));
	if(m_cosimbridge)
		m_dependencies.push_back(m_graph->GetTargetForArch("cosimbridge", arch));
	if(m_cosimbridge)
		m_dependencies.push_back(m_graph->GetTargetForArch("cosimbridge", arch));
	if(!m_scopenames.empty())
		m_dependencies.push_back(m_graph->GetTargetForArch("scopeclient", arch));
	if(!m_sniffers.empty())
		m_dependencies.push_back(m_graph->GetTargetForArch("nocsniff", arch));
	if(m_nocswitch)
		m_dependencies.push_back(m_graph->GetTargetForArch("nocswitch", arch));
		
	//Depend on the bitstream
	if(m_bitstream != NULL)
		m_dependencies.push_back(m_bitstream);
}

void FPGAUnitTestNode::SetBitstream(BuildGraphNode* bitstream)
{
	m_bitstream = bitstream;
}

void FPGAUnitTestNode::SetTestBinary(BuildGraphNode* binary)
{
	m_binary = binary;
}

void FPGAUnitTestNode::Update(Cluster* cluster)
{
	string outdir   = m_graph->GetMetaDir() + "/" + GetHashCached();
	string okpath   = outdir  + "/build_successful";
	string failpath = outdir  + "/build_failed";
	
	DebugPrintfSilent(5, "    Re-test is necessary\n");
	
	//Rebuild our dependencies (in case any source files were auto-generated, etc)
	vector<ClusterJob*> deps;
	for(size_t i=0; i<m_dependencies.size(); i++)
	{
		BuildGraphNode* node = m_dependencies[i];
		node->UpdateCached(cluster);
		if(node->GetBuildJob() != NULL)
			deps.push_back(node->GetBuildJob());
	}
	
	//Print the heading
	DebugPrintfHeading(3, "Running FPGA unit test %s\n", m_name.c_str());
	DebugPrintfSilent (5, "    command %s\n", m_command.c_str());
		
	//Run the test case
	string batch_fname = outdir + "/test_" + m_name + ".sh";
	FILE* fp = fopen(batch_fname.c_str(), "w");
	if(!fp)
		FatalError("Failed to create batch script %s\n", batch_fname.c_str());
	fprintf(fp, "#!/bin/bash\n");
	fprintf(fp, "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:%s/x86_64-linux-gnu\n", m_graph->GetOutDir().c_str());
	fprintf(fp, "%s --buildroot %s --testcase ", m_scriptPath.c_str(), m_graph->GetOutDir().c_str());
	if(m_args != "")
		fprintf(fp, "\"%s %s\" ", m_binary->GetOutputPath().c_str(), m_args.c_str());
	else if(m_binary != NULL)
		fprintf(fp, "\"%s\" ", m_binary->GetOutputPath().c_str());
	else
		fprintf(fp, "\"/bin/echo\" ");	//no test case, but we need to run something
	fprintf(fp, "--maxtime %d ", m_timelimit);
	if(m_bitstream != NULL)
		fprintf(fp, "--bitstream %s ", m_bitstream->GetOutputPath().c_str());
	if(m_nocswitch)
		fprintf(fp, "--nocswitch ");
	if(m_cosimbridge)
		fprintf(fp, "--cosimbridge ");
	for(auto x : m_scopenames)
		fprintf(fp, "--la %s ", x.c_str());
	for(auto x : m_sniffers)
		fprintf(fp, "--sniffer %s ", x.c_str());
	FPGABitstreamNode* bitstream = dynamic_cast<FPGABitstreamNode*>(m_bitstream);
	if(bitstream != NULL)
	{
		//Look to see if we generated it from a nocgen file
		for(auto x : bitstream->m_sources)
		{
			Nocgenv2GeneratedSourceNode* node = dynamic_cast<Nocgenv2GeneratedSourceNode*>(
				m_graph->GetNodeForPath(x, "generic"));
			if(node)
			{
				fprintf(fp, "--nocgen %s ", node->GetSourceFile()->GetOutputPath().c_str());
				break;
			}
		}
	}	
	fprintf(fp, "\n");
	fprintf(fp, "if [ $? -ne 0 ]; then\n");
	fprintf(fp, "    touch %s\n", failpath.c_str());
	fprintf(fp, "    exit 1\n");
	fprintf(fp, "else\n");
	fprintf(fp, "    touch %s\n", okpath.c_str());
	fprintf(fp, "fi\n");
	fclose(fp);
	if(0 != chmod(batch_fname.c_str(), 0755))
		FatalError("Failed to set execute permissions on batch script %s\n", batch_fname.c_str());
		
	//Submit the batch script
	m_job = cluster->SubmitBatchJob(batch_fname, m_partition, outdir, deps, 1, 0, m_features);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Factory method

FPGAUnitTestNode* FPGAUnitTestNode::CreateFPGAUnitTestNode(
	BuildGraph* graph,
	string args,
	string name,
	string partition,
	bool nocswitch)
{
	FPGAUnitTestNode* node = new FPGAUnitTestNode(
		graph, args, name, partition, nocswitch);
	graph->AddNode(node);
	graph->AddTest(node);
	return node;
}
